package com.spx.kernel.service;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import org.aopalliance.aop.Advice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

public class CoreServiceLocator
{
	
	Logger logger = Logger.getLogger(this.getClass().getName());
	
	
	private final ApplicationContext context;
	GenericApplicationContext gac;

	private static CoreServiceLocator instance;
	
	Set<String> registeredAdvisorNames = new HashSet<String>();

	final static private String configResource = "//var/projects/WMS/trunk/com.spx.kernel/src/com/spx/kernel/service/config.xml";

	public String getConfigResource()
	{
		return configResource;
	}

	private CoreServiceLocator()
	{

		context = new FileSystemXmlApplicationContext(configResource);
		gac = new GenericApplicationContext(context);
		final PersistService ps = this.getService("persist-service", PersistService.class);
		register("before-advisor", BeforeAdviser.class, ps);
		register("return-advisor", ReturnAdviser.class, this.getService("persist-service", PersistService.class));
		register("throw-advisor", ExceptionAdviser.class, this.getService("persist-service", PersistService.class));

	}

	@SuppressWarnings("unchecked")
	public <T> T getService(String name, Class<T> clazz)
	{

		T result = null;

		if (gac.getBean(name) != null && clazz.isAssignableFrom(gac.getBean(name).getClass()))
		{
			result = (T) gac.getBean(name);
		}
		return result;
	}

	public static synchronized CoreServiceLocator getInstance()
	{
		if (instance == null)
		{
			instance = new CoreServiceLocator();

		}
		return instance;
	}

	public void register(String name, Class<?> service, Object... constructorParams)
	{

		final boolean isAdvice = Advice.class.isAssignableFrom(service);

		doRegister(name, service, isAdvice, constructorParams);
	}

	private void doRegister(String name, Class<?> service, boolean isAdvisor, Object... constructorParams)
	{
		try
		{

			String targetName = name;
			if (isAdvisor == false)
			{
				targetName = name + "-target";
			} else 
			{
			   registeredAdvisorNames.add(name);	
			}

			final GenericBeanDefinition bd = new GenericBeanDefinition();
			bd.setBeanClassName(service.getName());
			bd.setFactoryBeanName(name);
			final GenericBeanDefinition proxyBd = new GenericBeanDefinition();
			if (constructorParams != null && constructorParams.length > 0)
			{
				final ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
				for (final Object value : constructorParams)
				{
					constructorArgumentValues.addGenericArgumentValue(value);
				}
				bd.setConstructorArgumentValues(constructorArgumentValues);

			}
			gac.registerBeanDefinition(targetName, bd);

			if (isAdvisor == false)
			{
				proxyBd.setBeanClassName(org.springframework.aop.framework.ProxyFactoryBean.class.getName());
				proxyBd.setAttribute("target", gac.getBean(targetName));
				final PropertyValue targetPropertyValue = new PropertyValue("target", gac.getBean(targetName));
				proxyBd.getPropertyValues().addPropertyValue(targetPropertyValue);

				
				
				Set<String> interceptorNameSet = new HashSet<String>();
				interceptorNameSet.add( "before-advisor");
				interceptorNameSet.addAll(registeredAdvisorNames);
				interceptorNameSet.add(  "return-advisor");
				interceptorNameSet.add(  "throw-advisor" );
				
				
//				String[] intercpetorNames = new String[] { "before-advisor", "return-advisor",
//						"throw-advisor" };
				String[] intercpetorNames=interceptorNameSet.toArray(new String[] {});
				final PropertyValue advisorPropertyValue = new PropertyValue("interceptorNames", intercpetorNames);
				proxyBd.getPropertyValues().addPropertyValue(advisorPropertyValue);

				gac.registerBeanDefinition(name, proxyBd);

			}

			gac.getBean(name);

		} catch (final Throwable t)
		{
			t.printStackTrace();
			throw new RuntimeException(t);
		}
	}

	static class ReturnAdviser implements AfterReturningAdvice
	{

		private final PersistService persistService;

		public ReturnAdviser(PersistService persistService)
		{
			this.persistService = persistService;
		}

		@Override
		public void afterReturning(Object arg0, Method method, Object[] arg2, Object arg3) throws Throwable
		{

			if (isBusiness(method))
			{
				System.out.println(method.getName() + "  ...has ended");
				persistService.commit();
			}

		}

	}

	public static class BeforeAdviser implements MethodBeforeAdvice
	{

		private final PersistService persistService;

		public BeforeAdviser(PersistService persistService)
		{
			this.persistService = persistService;
		}

		@Override
		public void before(Method method, Object[] arg1, Object arg2) throws Throwable
		{

			if (isBusiness(method))
			{
				persistService.begin();
				System.out.println(method.getName() + "  ...Is about to start");
			}
		}

	}

	public static class ExceptionAdviser implements ThrowsAdvice
	{

		private final PersistService persistService;

		public ExceptionAdviser(PersistService persistService)
		{
			this.persistService = persistService;
		}

		public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
		{

			if (isBusiness(method))
			{
				System.out.println(method.getName() + "  ...has thrown");
				ex.printStackTrace();
				persistService.rollback();
			}

		}

	}

	static private boolean isBusiness(Method method)
	{
		return Modifier.isPublic(method.getModifiers()) && (method.isAnnotationPresent(NonBusiness.class) == false);
	}
}
